/*
 * 文件名称: URL.java Copyright 2011-2016 Ximalaya All right reserved.
 */
package top.zxybird.yun.common;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;

/**
 * @author Ted Wang
 */
public class URL {
  private final String protocol; // protocol
  private final String group; // group
  private final String application; // 服务提供者或者消费者的名称
  private final String host; // 主机host
  private final Map<String, String> attributes;

  public URL(String protocol, String application, String host, String group) {
    this.protocol = protocol;
    this.application = application;
    this.host = host;
    this.group = group;
    this.attributes = Maps.newHashMap();
    setVersion();
  }

  public URL(String protocol, String application, String host, String group, Map<String, String> attributes) {
    this.protocol = protocol;
    this.application = application;
    this.host = host;
    this.group = group;
    this.attributes = attributes;
    setVersion();
  }

  private void setVersion() {
    this.attributes.put("version", Constants.VERSION);
  }

  public String getApplication() {
    return application;
  }

  public String getHost() {
    return host;
  }

  public String getGroup() {
    return group;
  }

  public String getProtocol() {
    return protocol;
  }

  public void setAttribute(String key, String value) {
    attributes.put(key, value);
  }

  public String getAttribute(String key) {
    return attributes.get(key);
  }

  public Map<String, String> getAttributes() {
    return Collections.unmodifiableMap(attributes);
  }

  public URL copy() {
    return new URL(this.protocol, this.application, this.host, this.group, new HashMap<>(getAttributes()));
  }

  public Boolean getBooleanAttribute(String key, boolean defaultValue) {
    String value = getAttribute(key);
    Boolean result = null;
    if (value != null) {
      result = Boolean.valueOf(value);
    } else {
      result = defaultValue;
    }
    return result;
  }

  public Boolean getBooleanAttribute(String key) {
    String value = getAttribute(key);
    if (value == null) {
      throw new IllegalArgumentException("key:" + key + " can not be empty");
    }
    return Boolean.valueOf(value);
  }

  public Integer getIntAttribute(String key, int defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    String value = getAttribute(key);
    if (value == null) {
      return defaultValue;
    }
    return Integer.parseInt(value);
  }

  public Integer getIntAttribute(String key) {
    String value = getAttribute(key);
    if (value == null) {
      throw new IllegalArgumentException("key:" + key + " can not be empty");
    }
    return Integer.parseInt(value);
  }

  public String getStringAttribute(String key, String defaultValue) {
    if (defaultValue.length() == 0) {
      throw new IllegalArgumentException("defaultValue is empty");
    }
    String value = getAttribute(key);
    if (value == null) {
      return defaultValue;
    }
    return value;
  }

  public String getStringAttributeWithoutCheck(String key, String defaultValue) {
    String value = getAttribute(key);
    if (value == null) {
      return defaultValue;
    }
    return value;
  }

  public String getStringAttribute(String key) {
    String value = getAttribute(key);
    if (value == null) {
      throw new IllegalArgumentException("attribute of " + key + " not exists!");
    }
    return value;
  }

  public String toUrlString() {
    StringBuilder sb = new StringBuilder();
    // thrift://group:application:host?key1=value1&key2=value2
    // 标准的URI格式？
    sb.append(protocol).append(Constants.COLON).append(Constants.PATH_SEPERATOR).append(Constants.PATH_SEPERATOR)
        .append(group).append(Constants.COLON).append(application).append(Constants.COLON).append(host);
    if (attributes.size() > 0) {
      sb.append(Constants.QUESTION_MARK);
      for (Entry<String, String> attribute : new TreeMap<>(attributes).entrySet()) {
        if (!attribute.getKey().startsWith(Constants.UNDER_LINE)) {
          sb.append(attribute.getKey()).append(Constants.EQ).append(attribute.getValue()).append(Constants.AND);
        }
      }
    }
    return sb.substring(0, sb.length() - 1);
  }

  public String toEncodeUrlString() {
    String url = toUrlString();
    String encodedUrl = null;
    try {
      encodedUrl = URLEncoder.encode(url, "UTF-8");
    } catch (UnsupportedEncodingException ignored) {
    }
    return encodedUrl;
  }

  /**
   * copy config from clientUrl to serviceUrl
   */
  public static void copyConfigForClient(URL serviceUrl, URL clientUrl) {
    updateInner(serviceUrl, clientUrl, URLParamType.iface.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.defaultTimeoutTs.getName());
    // for thrift
    updateInner(serviceUrl, clientUrl, URLParamType.transporter.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.ioThread.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.multiplex.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.timeout.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.requestConnectionTimeout.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.maxClientConnection.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.maxClientPendingAcquireSize.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.idleCheckIntervalSec.getName());
    updateInner(serviceUrl, clientUrl, URLParamType.enableNettyNative.getName());
  }

  /**
   * copy config from source to target if attribute exists
   */
  public static void updateInner(URL targetUrl, URL sourceUrl, String attribute) {
    String _attrVal = sourceUrl.getAttribute(attribute);
    if (_attrVal != null) {
      targetUrl.setAttribute(attribute, _attrVal);
    }
  }

  /**
   * eg. protocol://group:application:host?ip=192.168.3.24&port=9000&weight=50&type=provider
   */
  public static URL valueOf(String path) {
    Preconditions.checkNotNull(path, "path is null.");
    try {
      path = URLDecoder.decode(path, "UTF-8");
    } catch (UnsupportedEncodingException ignored) {
    }
    int protocolStop = path.indexOf("://");
    String protocol = path.substring(0, protocolStop);

    int questionMarkIdx = path.indexOf(Constants.QUESTION_MARK);
    Map<String, String> attributes = null;
    String attribute = null;
    if (questionMarkIdx > 0) {
      attributes = new HashMap<>();
      attribute = path.substring(questionMarkIdx + 1);
      String[] kvs = attribute.split(Constants.AND);
      for (String kv : kvs) {
        int eqIndex = kv.indexOf(Constants.EQ);
        String key = kv.substring(0, eqIndex);
        String value = kv.substring(eqIndex + 1);
        attributes.put(key, value);
      }
    }
    String root = path.substring(protocolStop + 3, questionMarkIdx);
    String[] rootSplit = root.split(Constants.COLON);
    String group = rootSplit[0];
    String application = rootSplit[1];
    String host = rootSplit[2];
    return new URL(protocol, application, host, group, attributes);
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((application == null) ? 0 : application.hashCode());
    result = prime * result + ((group == null) ? 0 : group.hashCode());
    result = prime * result + ((host == null) ? 0 : host.hashCode());
    result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
    String port = attributes.get(URLParamType.port.getName());
    result = prime * result + ((port == null) ? 0 : port.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    URL other = (URL) obj;
    if (application == null) {
      if (other.application != null)
        return false;
    } else if (!application.equals(other.application))
      return false;
    if (group == null) {
      if (other.group != null)
        return false;
    } else if (!group.equals(other.group))
      return false;
    if (host == null) {
      if (other.host != null)
        return false;
    } else if (!host.equals(other.host))
      return false;
    if (protocol == null) {
      if (other.protocol != null)
        return false;
    } else if (!protocol.equals(other.protocol))
      return false;
    String port = attributes.get(URLParamType.port.getName());
    String otherPort = other.attributes.get(URLParamType.port.getName());
    if (port == null) {
      return otherPort == null;
    } else
      return port.equals(otherPort);
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("URL [protocol=").append(protocol).append(", group=").append(group).append(", application=")
        .append(application).append(", host=").append(host).append(", attributes=").append(attributes).append("]");
    return builder.toString();
  }

  public String toPrettyString() {
    StringBuilder builder = new StringBuilder(1000);
    builder.append("\n");
    builder.append(String.format("%1$-30s: %2$10s\n", "protocol", protocol));
    builder.append(String.format("%1$-30s: %2$10s\n", "gruop", group));
    builder.append(String.format("%1$-30s: %2$10s\n", "application", application));
    for (Entry<String, String> entry : attributes.entrySet()) {
      builder.append(String.format("%1$-30s: %2$10s\n", entry.getKey(), entry.getValue()));
    }
    return builder.toString();
  }

}
